using System.CodeDom;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using EnvDTE;
using EnvDTE80;
using NUnitGenAddIn.Core;
using VSLangProj;

namespace NUnitGenAddIn.VS2008
{
    /// <summary>
    /// VS2008 Unit Test Generator
    /// </summary>
    public class VS2008UnitTestGenerator : UnitTestGenerator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="VS2008UnitTestGenerator"/> class.
        /// </summary>
        /// <param name="addInHelper">The add in helper.</param>
        /// <param name="unitTestGeneratorHelper">The unit test generator helper.</param>
        /// <param name="codeDomHelper">The code DOM helper.</param>
        public VS2008UnitTestGenerator(AddInHelper addInHelper, UnitTestGeneratorHelper unitTestGeneratorHelper,
                                       CodeDomHelper codeDomHelper)
            : base(addInHelper, unitTestGeneratorHelper, codeDomHelper)
        {}

        /// <summary>
        /// Generates the test method.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <param name="testClass">The test class.</param>
        /// <param name="testMethodName">Name of the test method.</param>
        protected override void GenerateTestMethod(CodeFunction targetFunction, CodeClass testClass, string testMethodName)
        {
            CodeFunction testFunction =
                testClass.AddFunction(testMethodName, vsCMFunction.vsCMFunctionFunction, 
                                      vsCMTypeRef.vsCMTypeRefVoid, -1,vsCMAccess.vsCMAccessPublic, 0);
            testFunction.AddAttribute("Test", "", 0);
            testFunction.DocComment = DocumentationHelper.GetDocumentationHeaderString(testMethodName);            
            _addInHelper.GetActiveDocument().Save("");

            TextPoint functionStartPoint = testFunction.GetStartPoint(vsCMPart.vsCMPartBody);
            EditPoint editPoint = functionStartPoint.CreateEditPoint();
            editPoint.Insert(GenerateTestMethodBodyFromTargetFunction(targetFunction));
            _addInHelper.ExecuteCommand("Edit", "FormatDocument", "");
            _addInHelper.GetActiveDocument().Save("");
            ((TextSelection) _addInHelper.GetActiveDocument().Selection).GotoLine(editPoint.Line, false);
        }

        /// <summary>
        /// Adds the new test fixture file.
        /// </summary>
        /// <param name="testClassName">Name of the test class.</param>
        /// <param name="targetClass">The target class.</param>
        protected override void AddNewTestFixtureFile(string testClassName, CodeClass targetClass)
        {
            Project project = targetClass.ProjectItem.ContainingProject;
            const string testFolderName = "Test";
            string testFilename = testClassName + ".cs";
            Solution2 solution = _addInHelper.GetSolution() as Solution2;

            if (solution != null)
            {
                string csharpCodeFileTemplatePath = solution.GetProjectItemTemplate("CodeFile", "CSharp");
                SourceControl scc = _addInHelper.GetSourceControl();
                string itemName = project.FullName;

                if (scc.IsItemUnderSCC(itemName) && !scc.IsItemCheckedOut(itemName))
                {
                    scc.CheckOutItem(itemName);
                }

                string name =  targetClass.Namespace.FullName;

                string[] names = name.Split('.');

                ProjectItem testFolder = null;
                try
                {
                    testFolder = project.ProjectItems.Item(testFolderName);
                }
                catch
                {}

                if (testFolder == null)
                {
                    testFolder = project.ProjectItems.AddFolder(testFolderName, "");
                    ProjectItem folder = null;

                    foreach (string space in names)
                    {
                        try
                        {
                            folder = testFolder.ProjectItems.Item(space);
                        }
                        catch
                        { }

                        testFolder = folder ?? testFolder.ProjectItems.AddFolder(space, "");
                    }
                }
                else
                {
                    ProjectItem folder = null;

                    foreach (string space in names)
                    {
                        try
                        {
                            folder = testFolder.ProjectItems.Item(space);
                        }
                        catch
                        { }

                        testFolder = folder ?? testFolder.ProjectItems.AddFolder(space, "");
                    }
                }

                testFolder.ProjectItems.AddFromTemplate(csharpCodeFileTemplatePath, testFilename);
            }

            VSProject vsProject = project.Object as VSProject;
            try
            {
                if (vsProject != null)
                {
                    vsProject.References.Add(_unitTestGeneratorHelper.NUnitFrameworkDllPath);
                }
            }
            catch
            {}
        }

        /// <summary>
        /// Determines whether if the function is a constructor.
        /// </summary>
        /// <param name="codeFunction">The code function.</param>
        /// <returns>
        /// 	<c>true</c> if the function is a constructor otherwise, <c>false</c>.
        /// </returns>
        protected override bool IsFunctionConstructor(CodeFunction codeFunction)
        {
            return codeFunction.FunctionKind == vsCMFunction.vsCMFunctionConstructor;
        }

        /// <summary>
        /// Generates the test method body statements from target function.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <returns><see cref="CodeStatementCollection"/> object</returns>
        protected override CodeStatementCollection GenerateTestMethodBodyStatementsFromTargetFunction(
                                                   CodeFunction targetFunction)
        {
            string targetTypeName = ((CodeElement) targetFunction.Parent).Name;
            CodeStatementCollection statementCollection = new CodeStatementCollection();
            CodeExpression[] funcParamsArray = PrepareParameters(targetFunction, statementCollection);

            if (targetFunction.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
                ProcessConstructor(targetTypeName, statementCollection, funcParamsArray);
            else
                ProcessOtherMethod(targetFunction, targetTypeName, statementCollection, funcParamsArray);

            statementCollection.Add(_unitTestGeneratorHelper.CreateTestFailMethodInvokeExpression("Create or modify test(s)."));
            return statementCollection;
        }

        /// <summary>
        /// Processes the parameters.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <param name="statementCollection">The statement collection.</param>
        protected override void ProcessParameters(CodeFunction targetFunction, CodeStatementCollection statementCollection)
        {
            foreach (CodeParameter2 param in targetFunction.Parameters)
            {
                if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindOut)
                {
                    statementCollection.Add(
                        _unitTestGeneratorHelper.CreateIsNotNullMethodInvokeExpression("expected" + param.Name,
                                                                                      param.Name + " out parameter should not be null"));
                }
                else if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindRef)
                {
                    statementCollection.Add(
                        _unitTestGeneratorHelper.CreateAreEqualMethodInvokeExpression("expected" + param.Name, param.Name,
                                                                                     param.Name + " ref parameter has unexpected result."));
                }
            }
        }

        /// <summary>
        /// Prepares the parameters.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <param name="statementCollection">The statement collection.</param>
        /// <returns></returns>
        protected override CodeExpression[] PrepareParameters(CodeFunction targetFunction,
                                                              CodeStatementCollection statementCollection)
        {
            CodeExpressionCollection funcParams = new CodeExpressionCollection();
            foreach (CodeParameter2 param in targetFunction.Parameters)
            {
                statementCollection.Add(_codeDomHelper.CreateVariableDeclarationStatement(param.Type, param.Name));
                if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindOut
                    || param.ParameterKind == vsCMParameterKind.vsCMParameterKindRef)
                    statementCollection.Add(_codeDomHelper.CreateVariableDeclarationStatement(param.Type, "expected" + param.Name));
                CodeExpression paramVariableExpression = new CodeVariableReferenceExpression(param.Name);
                if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindOut)
                    funcParams.Add(new CodeDirectionExpression(FieldDirection.Out, paramVariableExpression));
                else if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindRef)
                    funcParams.Add(new CodeDirectionExpression(FieldDirection.Ref, paramVariableExpression));
                else
                    funcParams.Add(paramVariableExpression);
            }
            CodeExpression[] funcParamsArray = new CodeExpression[funcParams.Count];
            funcParams.CopyTo(funcParamsArray, 0);
            return funcParamsArray;
        }

        /// <summary>
        /// Gets the type list from code elements.
        /// </summary>
        /// <param name="typeList">The type list.</param>
        /// <param name="codeElements">The code elements.</param>
        protected override void  GetTypeListFromCodeElements(ArrayList typeList, CodeElements codeElements)
        {
            foreach (CodeElement element in codeElements)
            {
                if (element.Kind == vsCMElement.vsCMElementClass)
                {
                    CodeClass type = element as CodeClass;
                    if (IsTypeEligibleForUnitTestGeneration(type))
                        typeList.Add(type);
                }
                if (element.Children.Count > 0)
                    GetTypeListFromCodeElements(typeList, element.Children);
            }
        }        
    }
}